• 问题

    数组和泛型化的列表在实际使用中经常会被用来装载数据元素,但是不清楚数组和泛型化List之间的区别,经常会造成数组和List的混用;

  • 原因及解决方法

    数组和泛型化数组的主要区别有:

    1. 数组是协变的而泛型是不可变的,协变的意思是如果Sub为super的子类型,那么数组类型Sub[]就是super[]的子类型,不可变的意思是对于任意两个不同的类型Type1和Type2,List既不是List的子类型,也不是List的超类型。可以看下面这个例子:

      // 数组是协变的,因此编译可以通过,但是会出现运行时异常
      Object[] objectArray = new Long[1];
      objectArray[0] = "I don't fit in"; // Throws ArrayStoreException
      
      // List是不可变的,编译无法通过
      List<Object> ol = new ArrayList<Long>(); // Incompatible types
      ol.add("I don't fit in");
      

      从上例的代码可以看出,数组是协变的,因为Long是Object的子类型,所以Long[]是Object[]的子类型,第一段代码会编译通过。但是运行时,objectArray运行时类型是Long的List,插入String类型的数据就会抛出ArrayStoreException。而List是不可变的,泛型在运行时会类型擦除,类型检查会放在编译期间,因此第二段代码在编译期间就不会通过,将问题暴露在编译期间,这样才是可靠的。

    2. 数组是具体化的(reified),数组在运行时才会约束数据类型是否匹配。泛型则是通过擦除(erasure)来实现,因此泛型只在编译时强化它们的类型信息,并在运行时丢弃(或者擦除)它们的元素类型约束。类型擦除主要是为了兼容之前没有泛型特性的代码。

    3. 基于以上两点原因,创建泛型、参数化类型或者是类型参数的数组都是非法的。如:new List[]、new List[]和new E[]都是非法的。这些在编译的时候会产生一个generic array creating错误。为什么会禁止行为?

      //假设可以创建参数化类型数组
      List<String>[] stringLists = new List<String>[1];
      List<Integer> intList = Arrays.asList(42);
      //由于数组是协变的,可以赋值
      Object[] objects = stringLists;
      //数组赋值在运行时进行,由于initList在运行时会擦除
      //类型信息,转换为List,因此可以插入到数组中
      objects[0] = intList;
      //取数据时目标类型是String,但是实际类型为Integer,
      //因此就会出现ClassCastException
      String s = stringLists[0].get(0);
      

      针对上面的分析,可以看出禁止创建参数化类型数组等情况是合理的,能够将错误暴露在编译期。像E、List、List这样的类型应称作不可具体化的类型。唯一可具体化的参数化类型是无限制的通配符类型,如List<?>和Map<?,?>。虽然不常用,但是创建无限制通配类型的数组是合法的。

  • 结论

    数组和泛型有着非常不同的类型规则。数组是协变且可具体化的,泛型是不可变的且是可被擦除的。因此,数组提供了运行时的类型安全,但是没有编译时的类型安全,反之,泛型也一样,一般来说,数组和泛型不能很好的混合使用,如果发现自己将它们混合起来使用,并且得到了编译时错误或警告,那么就要考虑用列表代替数组

results matching ""

    No results matching ""